package com.zeta.system.controller;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.read.listener.ReadListener;
import cn.idev.excel.write.handler.WriteHandler;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zeta.api.model.UserInfoDTO;
import com.zeta.system.model.dto.SysRoleDTO;
import com.zeta.system.model.dto.SysUserDTO;
import com.zeta.system.model.entity.SysMenu;
import com.zeta.system.model.entity.SysUser;
import com.zeta.system.model.enums.MenuTypeEnum;
import com.zeta.system.model.enums.SexEnum;
import com.zeta.system.model.param.*;
import com.zeta.system.model.poi.SysUserExportVO;
import com.zeta.system.model.poi.SysUserImportVO;
import com.zeta.system.model.query.SysUserQueryDTO;
import com.zeta.system.model.vo.FrontRoute;
import com.zeta.system.model.vo.SysUserPageVO;
import com.zeta.system.poi.listener.SysUserImportListener;
import com.zeta.system.service.ISysRoleMenuService;
import com.zeta.system.service.ISysRoleService;
import com.zeta.system.service.ISysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.context.ApplicationContext;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.zetaframework.annotation.log.SysLog;
import org.zetaframework.annotation.satoken.PreAuth;
import org.zetaframework.annotation.satoken.PreCheckPermission;
import org.zetaframework.base.excel.handler.DropdownHandler;
import org.zetaframework.base.exception.BusinessException;
import org.zetaframework.base.exception.ExcelBusinessException;
import org.zetaframework.base.param.ExistParam;
import org.zetaframework.base.param.PageParam;
import org.zetaframework.base.param.UpdateStateParam;
import org.zetaframework.base.result.ApiResult;
import org.zetaframework.base.result.PageResult;
import org.zetaframework.base.utils.TreeUtil;
import org.zetaframework.controller.SuperNoQueryController;
import org.zetaframework.controller.extra.ExistenceController;
import org.zetaframework.controller.extra.PoiController;
import org.zetaframework.controller.extra.UpdateStateController;
import org.zetaframework.core.desensitization.utils.DesensitizationUtil;
import org.zetaframework.core.enums.LoginStateEnum;
import org.zetaframework.core.enums.UserStateEnum;
import org.zetaframework.core.model.LoginLogDTO;
import org.zetaframework.core.utils.ContextUtil;
import org.zetaframework.log.event.LoginEvent;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 用户 前端控制器
 *
 * @author AutoGenerator
 */
@RequiredArgsConstructor
@Tag(name = "用户", description = "10.用户")
@PreAuth(replace = "sys:user")
@RestController
@RequestMapping("/system/user")
public class SysUserController extends SuperNoQueryController<ISysUserService, Long, SysUser, SysUserSaveDTO, SysUserUpdateDTO>
    implements UpdateStateController<SysUser, Long, Integer>,
        ExistenceController<SysUser, Long>,
        PoiController<SysUserImportVO, SysUserExportVO, SysUser, SysUserQueryDTO>
{
    private final ISysRoleService roleService;
    private final ISysRoleMenuService roleMenuService;
    private final ApplicationContext applicationContext;

    /**
     * 分页查询
     *
     * @param param 分页查询参数
     */
    @PreCheckPermission(value = "{}:view")
    @Operation(summary = "分页查询")
    @SysLog(response = false)
    @PostMapping("/page")
    public ApiResult<PageResult<SysUserPageVO>> page(@RequestBody PageParam<SysUserQueryDTO> param) {
        return success(service.customPage(param));
    }

    /**
     * 单体查询（不返回用户密码）
     *
     * @param id Id 主键
     */
    @PreCheckPermission(value = "{}:view")
    @Operation(summary = "单体查询", description = "不返回用户密码")
    @SysLog
    @GetMapping("/{id}")
    public ApiResult<SysUserDTO> get(@PathVariable("id") @Parameter(description = "主键") Long id) {
        SysUser entity = getBaseService().getById(id);
        // 处理单体查询数据
        handlerGetData(entity);
        // Entity -> EntityDTO
        SysUserDTO entityDTO = BeanUtil.copyProperties(entity, SysUserDTO.class);
        return success(entityDTO);
    }

    /**
     * 处理单体查询数据
     *
     * @param sysUser Entity
     */
    private void handlerGetData(SysUser sysUser) {
        if (ObjectUtil.isNull(sysUser)) { return; }

        // 查询并设置用户拥有的角色
        List<SysRoleDTO> userRoles = service.getUserRoles(sysUser.getId());
        sysUser.setRoles(userRoles);
    }


    /**
     * 批量查询（不返回用户密码）
     *
     * @param param SysUserQueryDTO 批量查询参数
     */
    @PreCheckPermission(value = "{}:view")
    @Operation(summary = "批量查询", description = "不返回用户密码")
    @SysLog(response = false)
    @PostMapping("/query")
    public ApiResult<List<SysUserDTO>> list(@RequestBody SysUserQueryDTO param) {
        SysUser entity = BeanUtil.toBean(param, getEntityClass());
        // 批量查询
        List<SysUser> list = getBaseService().list(new QueryWrapper<>(entity));
        // 处理批量查询数据
        handlerBatchData(list);
        // Entity -> EntityDTO
        List<SysUserDTO> result = BeanUtil.copyToList(list, SysUserDTO.class);
        return success(result);
    }

    /**
     * 处理批量查询数据
     *
     * @param list 用户列表
     */
    private void handlerBatchData(List<SysUser> list) {
        if (list.isEmpty()) { return; }
        List<Long> userIds = list.stream().map(SysUser::getId).collect(Collectors.toList());

        // 批量获取用户角色 Map<用户id, 用户角色列表>
        Map<Long, List<SysRoleDTO>> userRoleMap = service.getUserRoles(userIds);
        list.forEach(user -> {
            user.setRoles(userRoleMap.getOrDefault(user.getId(), Collections.emptyList()));
        });
    }

    /**
     * 自定义新增
     *
     * @param saveDTO SaveDTO 保存对象
     */
    @Override
    public ApiResult<Boolean> handlerSave(SysUserSaveDTO saveDTO) {
        // 判断是否存在
        if (new ExistParam<SysUser, Long>(SysUser::getAccount, saveDTO.getAccount()).isExist(service)) {
            return fail("账号已存在");
        }

        return success(service.saveUser(saveDTO));
    }

    /**
     * 自定义修改
     *
     * @param updateDTO UpdateDTO 修改对象
     */
    @Override
    public ApiResult<Boolean> handlerUpdate(SysUserUpdateDTO updateDTO) {
        return success(service.updateUser(updateDTO));
    }

    /**
     * 自定义修改状态
     *
     * @param param UpdateStateParam<Id, State> 修改对象
     */
    @Override
    public ApiResult<Boolean> handlerUpdateState(UpdateStateParam<Long, Integer> param) {
        // 判断状态值是否正常
        Assert.notNull(param.getId(), "用户id不能为空");
        Assert.notNull(param.getState(), "状态不能为空");

        // 判断状态参数是否在定义的用户状态中
        if (!UserStateEnum.getAllCode().contains(param.getState())) {
            return fail("参数异常");
        }

        // 判断用户是否允许修改
        SysUser user = service.getById(param.getId());
        if (user == null) { return fail("用户不存在", false); }
        if (user.getReadonly() != null && user.getReadonly()) {
            throw new BusinessException("用户[]禁止修改状态", user.getUsername());
        }

        // 修改状态
        return UpdateStateController.super.handlerUpdateState(param);
    }

    /**
     * 自定义单体删除
     *
     * @param id Id
     */
    @Override
    public ApiResult<Boolean> handlerDelete(Long id) {
        SysUser user = service.getById(id);
        if (user == null) { return success(true); }

        // 判断用户是否允许删除
        if (user.getReadonly() != null && user.getReadonly()) {
            throw new BusinessException("用户[{}]禁止删除", user.getUsername());
        }

        return super.handlerDelete(id);
    }

    /**
     * 自定义批量删除
     *
     * @param ids Set<Long>
     */
    @Override
    public ApiResult<Boolean> handlerBatchDelete(Set<Long> ids) {
        List<SysUser> userList = service.listByIds(ids);
        if (userList.isEmpty()) { return success(true); }

        // 判断是否存在不运行删除的用户
        userList.forEach(user -> {
            if (user.getReadonly() != null && user.getReadonly()) {
                throw new BusinessException("用户[{}]禁止删除", user.getUsername());
            }
        });

        return super.handlerBatchDelete(ids);
    }

    /**
     * 修改自己的密码
     *
     * @param param 修改密码的参数
     * @param request HttpServletRequest
     */
    @Operation(summary = "修改自己的密码")
    @ResponseBody
    @PutMapping("/change/password")
    public ApiResult<Boolean> changePwd(@RequestBody @Validated ChangePasswordDTO param, HttpServletRequest request) {
        SysUser user = service.getById(ContextUtil.getUserId());
        if (user == null) { throw new BusinessException("用户不存在"); }

        // 旧密码是否正确
        if (!service.comparePassword(param.getOldPwd(), user.getPassword())) {
            return fail("旧密码不正确", false);
        }

        // 修改成新密码
        user.setPassword(service.encodePassword(param.getNewPwd()));
        if (!service.updateById(user)) {
            return fail("修改失败", false);
        }

        // 登出日志
        LoginEvent event = new LoginEvent(LoginLogDTO.loginFail(user.getAccount(), LoginStateEnum.LOGOUT, "修改密码", request));
        applicationContext.publishEvent(event);

        // 下线
        StpUtil.logout(user.getId());
        return success("修改成功", true);
    }

    /**
     * 重置密码
     *
     * @param param ResetPasswordParam 重置密码参数
     */
    @PreCheckPermission(value = "{}:update")
    @Operation(summary = "重置密码")
    @PutMapping("/reset/password")
    public ApiResult<Boolean> updatePwd(@RequestBody @Validated ResetPasswordDTO param, HttpServletRequest request) {
        SysUser user = service.getById(param.getId());
        if (user == null) { return success(true); }

        // 判断用户是否允许重置密码
        if (user.getReadonly() != null && user.getReadonly()) {
            throw new BusinessException("用户[]禁止重置密码", user.getUsername());
        }

        // 密码加密
        user.setPassword(service.encodePassword(param.getPassword()));
        // 修改密码
        if (!service.updateById(user)) {
            return fail("重置密码失败");
        }

        // 登出日志
        LoginEvent event = new LoginEvent(LoginLogDTO.loginFail(user.getAccount(), LoginStateEnum.LOGOUT, "重置密码", request));
        applicationContext.publishEvent(event);

        // 让被修改密码的人下线
        StpUtil.logout(user.getId());
        return success(true);
    }

    /**
     * 修改用户基础信息（右上角用户信息页面-保存修改时调用）
     *
     * @param param 修改用户基本信息参数
     */
    @Operation(summary = "修改用户基础信息")
    @PutMapping("/change/info")
    public ApiResult<Boolean> changeUserBaseInfo(@RequestBody @Validated ChangeUserBaseInfoDTO param) {
        SysUser user = service.getById(ContextUtil.getUserId());
        if (user == null) { return fail("用户不存在"); }

        // 判断用户是否允许修改
        if (user.getReadonly() != null && user.getReadonly()) {
            throw new BusinessException("用户[]禁止修改", user.getUsername());
        }

        // param -> entity
        BeanUtil.copyProperties(param, user);

        // 修改用户信息
        boolean result = service.updateUserBaseInfo(user);
        return  result ? success("修改成功", true) : fail("修改失败", false);
    }

    /**
     * 获取当前用户基本信息
     */
    @Operation(summary = "获取当前用户基本信息")
    @GetMapping("/info")
    public ApiResult<UserInfoDTO> userInfo(){
        SysUser user = service.getById(ContextUtil.getUserId());
        if (user == null) { return fail("用户不存在"); }
        UserInfoDTO userInfoDTO = BeanUtil.toBean(user, UserInfoDTO.class);

        // 获取用户角色列表
        userInfoDTO.setRoles(StpUtil.getRoleList());
        // 获取用户权限列表
        userInfoDTO.setPermissions(StpUtil.getPermissionList());
        return success(userInfoDTO);
    }

    /**
     * 获取当前用户菜单
     */
    @Operation(summary = "获取当前用户菜单")
    @GetMapping("/menu")
    public ApiResult<List<FrontRoute>> userMenu() {
        // 查询用户对应的菜单
        List<SysMenu> menuList = roleMenuService.listMenuByUserId(ContextUtil.getUserId(), MenuTypeEnum.MENU.name());

        // List<SysMenu> -> List<FrontRoute>
        List<FrontRoute> frontRouteList = menuList.stream()
                .map(FrontRoute::convert)
                .collect(Collectors.toList());
        return success(TreeUtil.buildTree(frontRouteList, false));
    }

    /**
     * <p>注册导出Excel模板 自定义写处理器</p>
     *
     * 说明：<br>
     * 如果你的excel模板里面需要有一些额外信息，请重写本方法
     * <p>
     * 参考：<br>
     * <a href="https://fast-excel.github.io/fastexcel/zh-cn/docs/write/extra#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8">自定义拦截器</a>
     *
     * @param handlers 写处理器列表
     */
    @Override
    public void registerImportWriteHandlers(List<WriteHandler> handlers) {
        // 注册-下拉框处理器（用户性别）
        handlers.add(new DropdownHandler(
                // 用户性别所在列，F2:F2(因为是导出模板，只取了一行)
                new CellRangeAddressList(1, 1, 5, 5),
                SexEnum.getAllMsg()
        ));

        // 注册-下拉框处理器（用户角色）
        handlers.add(new DropdownHandler(
                // 用户角色所在列，H2:H2(因为是导出模板，只取了一行)
                new CellRangeAddressList(1, 1, 7, 7),
                roleService.getAllRoleNames())
        );
    }

    /**
     * <p>注册导入数据监听器</p>
     * <p>
     * 说明：
     * <pre>
     * 1.你需要手动实现导入监听器，来处理导入的数据
     * 2.不要将Listener交给Spring管理。
     * </pre>
     * 相关文档：
     * <a href="https://fast-excel.github.io/fastexcel/zh-cn/docs/help/faq#%E5%B9%B6%E5%8F%91%E8%AF%BB%E5%8F%96">为何不能将Listener交给Spring管理？</a>
     */
    @Override
    public ReadListener<SysUserImportVO> getReadListener() {
        return new SysUserImportListener(service);
    }

    /**
     * 获取待导出的数据
     *
     * @param param 查询参数 不可为空
     * @return List<ExportBean>
     */
    @Override
    public List<SysUserExportVO> findExportList(SysUserQueryDTO param) {
        // 判断状态参数是否在定义的用户状态中
        if (param.getState() != null && !UserStateEnum.getAllCode().contains(param.getState())) {
            throw new ExcelBusinessException("状态参数异常");
        }

        // 条件查询Entity数据
        SysUser entity = BeanUtil.toBean(param, SysUser.class);
        List<SysUser> list = service.list(new LambdaQueryWrapper<>(entity));
        handlerBatchData(list);
        if (list.isEmpty()) { return Collections.emptyList(); }

        // List<Entity> -> List<ExportVO>
        return list.stream().map(user-> {
            SysUserExportVO exportVO = BeanUtil.toBean(user, SysUserExportVO.class);
            // 密码脱敏
            String deserializationPwd = DesensitizationUtil.deserialization(exportVO.getPassword(), "3_4", "*");
            exportVO.setPassword(deserializationPwd);

            // 处理用户角色 ps:导出角色名还是导出角色编码看需求
            List<String> roles = user.getRoles().stream()
                    .map(SysRoleDTO::getName)
                    .filter(StrUtil::isNotBlank)
                    .collect(Collectors.toList());

            // 设置用户角色
            exportVO.setRoles(CollUtil.join(roles, StrUtil.COMMA));
            return exportVO;
        }).toList();
    }
}

